package birdsVSZombiesServer;

import java.util.ArrayList;
import java.util.LinkedList;

import birdsVSZombiesClient.GUI.BVZButton;
import birdsVSZombiesClient.GUI.BVZRectButton;

import com.google.gson.Gson;

import pbox2d.PBox2D;
import jbox2dprocessing.compontent.*;

import processing.core.PApplet;
import processing.core.PFont;
import processing.net.Client;
import processing.net.Server;
import shared.BVZClientToServerMessages;
import shared.BVZConstants;
import shared.BVZServerToClientsMessages;

/**
 * The server class which runs the server and physical simulation.
 * @author Yang Liu
 * @version Version 0.0.4, 11/22/2010
 */
public class BirdsVSZombiesServer extends PApplet{
	
	public Server bvzServer;
	
	/*
	 * Variables for Server Status
	 */
	public int serverStat = INITCONNECTED;
	public static final int INITCONNECTED = 0;
	public static final int INITPHYS = 1;
	public static final int RUNPHYS = 2;
	
	/*
	 * Variables for Player Information
	 */
//	public LinkedList<BVZPlayer> bvzPlayerList = new LinkedList<BVZPlayer>();
	public BVZPlayer[] bvzPlayerList = new BVZPlayer[2];
	public int bvzPlayerNum = 0;
	
	/*
	 * Variables for physical stuff
	 */
	public PBox2D box2d;
	public LinkedList<AbstractBox2DShape> boxes;
	public LinkedList<BoundaryBox> boundaries;
	private LinkedList<BaseCircle> circles;
	
	public MouseInteraction[] mouseJoints = new MouseInteraction[2];
	
	/*
	 * Variables for Json Messages
	 */
	public Gson gson;
	public BVZClientToServerMessages incomingMessage;
	
	/*
	 * Server GUI
	 */
	public BVZRectButton startButton;
	public PFont f;
	
	
	public void setup(){
		size(800,600);
		frameRate(60);
		bvzServer = new Server(this, 5207);
		f = createFont("Arial", 20, true);
		
		AbstractBox2DShape.parent = this;
		BVZButton.parent = this;
		
		gson = new Gson();
		
		startButton = new BVZRectButton("Click to start!");
		
	}
	
	public void draw(){
		background(255);
		smooth();
		
		serverSelfUpdate();
		
		listenToClients();
		
		startButton.display();
		startButton.update();
	}
	
	/**
	 * Update server according to different server status.
	 */
	public void serverSelfUpdate(){
		switch (serverStat) {
			case INITCONNECTED:
				break;
			case INITPHYS:
				initPhysWorld();
				break;
			case RUNPHYS:
				runASimplePhys();
				break;
			default:
				break;
		}
	}
	
	/**
	 * Listen to the messages from clients.
	 */
	public void listenToClients(){
		Client bvzClient = bvzServer.available();
		if(bvzClient != null){
			String dataIn = bvzClient.readStringUntil('*');
			if(dataIn != null){
				dataIn = dataIn.substring(0, dataIn.length() -1);
				incomingMessage = gson.fromJson(dataIn, BVZClientToServerMessages.class);
				
//				println("Client says: " + dataIn);
				
				switch (incomingMessage.clientStat) {
					case BVZConstants.CLIENTSTART:
						makeBVZPlayer(bvzClient, 0);
						makeBVZPlayer(bvzClient, 1);
						println("Clients: "+ bvzClient.ip() +" started!!!");
						//	A test for server phys simulation
						callRunASimplePhys();
						
						break;
					case BVZConstants.CLIENTJOININ:
						makeBVZPlayer(bvzClient, incomingMessage.playerRole);
						if(this.bvzPlayerNum == 2)
							callRunASimplePhys();
						break;
					case BVZConstants.CLIENTRUNNINGPHYS:
						switch (incomingMessage.mouseStat) {
							case BVZConstants.MOUSEPRESSED:
								createMouseJoint();
								println("Mouse Joint Created!!!!");
								break;
							case BVZConstants.MOUSEDRAGGED:
								updateMouseJoint();
								println("Mouse Joint Updating!!!!");
								break;
							case BVZConstants.MOUSERELEASED:
								destoryMouseJoint();
								println("Mouse Joint Destoried!!!!");
								break;
							default:
								break;
						}
						
						break;
					default:
						break;
				}
			}
			else{
				println("No data Recieved!!!");
			}
		}
	}
	
	/**
	 * Change the server status to start the physical simulation.
	 */
	public void callRunASimplePhys(){
		serverStat = INITPHYS;
	}
	
	/**
	 * Initialize the physical world.
	 */
	public void initPhysWorld(){
		box2d = new PBox2D(this);
		box2d.createWorld();
		box2d.setGravity(0, -50);
		
		boundaries = new LinkedList<BoundaryBox>();
		boxes = new LinkedList<AbstractBox2DShape>();
		circles = new LinkedList<BaseCircle>();
		
		BoundaryBox bottomBoundary = new BoundaryBox(400, 575, 800, 50, box2d);
		boundaries.add(bottomBoundary);
		
		BoundaryBox bottomBoundary2 = new BoundaryBox(400, 200, 50, 400, box2d);
		boundaries.add(bottomBoundary2);
		
		PhysCircle p = new PhysCircle(100, height/2, 50, box2d, bvzPlayerList[0].playerRole, 1.0f, 0.3f, 0.3f);
		circles.add(p);
		
		PhysCircle p2 = new PhysCircle(400, height/2, 50, box2d, bvzPlayerList[1].playerRole, 1.0f, 0.3f, 0.3f);
		circles.add(p2);
		
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERPHYSINITCOMPLETED);
		String msgJson = gson.toJson(msg) + "*";
		bvzServer.write(msgJson);
		
		serverStat = RUNPHYS;
	}
	
	/**
	 * Run a simple physical simulation and send data to Clients.
	 */
	public void runASimplePhys(){
		box2d.step();
		
		for (int i = 0; i < boundaries.size(); i++) {
			BoundaryBox bb = boundaries.get(i);
			bb.display();
		}
		
		//		Display the PhysCircles
		for (int i = 0; i < circles.size(); i++) {
			BaseCircle c = (BaseCircle)circles.get(i);
			c.display();
		}
		
		//		Make a snap shot of the scene
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERRUNPHYS, boundaries, circles);
		String msgJson = gson.toJson(msg) + "*";
//		println(msgJson);
		bvzServer.write(msgJson);
	}
	
	public void serverEvent(Server someServer, Client someClient){
		//		Make a welcome json message for new connecter
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERWELCOMEMESSAGE);
		String msgJson = gson.toJson(msg) + "*";
		
		println(msgJson);
		
		someClient.write(msgJson);
	}
	
	/**
	 * Add players when a new client connected.
	 * @param bvzClient The clients which the current player is in.
	 */
	public void makeBVZPlayer(Client bvzClient, int playerRole){
		BVZPlayer bvzPlayer = new BVZPlayer(playerRole, bvzClient);
		bvzPlayerList[playerRole] = bvzPlayer;
				
		println("we have a new " + bvzPlayer.playerTitle + "!" + bvzPlayerList[playerRole].playerClient.ip());
		
		bvzPlayerNum++;
		
		//	Make a welcome json message for new players
//		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERWELCOMEMESSAGE);
//		String msgJson = gson.toJson(msg) + "*";
//		
//		println(msgJson);
//		
//		bvzPlayer.playerClient.write(msgJson);
	}
	
	/**
	 * Construct the message the server will send back to clients
	 */
	public void makeMessages(){
		// TODO make this function
	}
	
	/**
	 * Create a mouse joint only when the shape is belong to the player.
	 */
	public void createMouseJoint(){
		mouseJoints[incomingMessage.playerRole] = new MouseInteraction(box2d);
		if(mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage) != null && mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage).bodyMaster == incomingMessage.playerRole){
			mouseJoints[incomingMessage.playerRole].bind(incomingMessage.mousePosX, incomingMessage.mousePosY, mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage));
			mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
			println("Mouse Joint Created!!!!");
		}
	}
	
	/**
	 * Update a mouse joint
	 */
	public void updateMouseJoint(){
		mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
		println("Mouse Joint Updating!!!!");
	}
	
	/**
	 * Destory a mouse joint
	 */
	public void destoryMouseJoint(){
		mouseJoints[incomingMessage.playerRole].destory();
		println("Mouse Joint Destoried!!!!");
	}
	
	public static void main(String _args[]) {
		PApplet.main(new String[] { birdsVSZombiesServer.BirdsVSZombiesServer.class.getName() });
	}
}
